package org.javahispano.javacup.tacticas_aceptadas.kalimero.org.jpenalta.javacup.tactica.kalimero;

import java.util.ArrayList;

import org.javahispano.javacup.modelo.Constantes;
import org.javahispano.javacup.modelo.Partido;
import org.javahispano.javacup.modelo.Posicion;
import org.javahispano.javacup.modelo.SituacionPartido;
import org.javahispano.javacup.modelo.Tactica;
import org.javahispano.javacup.modelo.TacticaDetalle;

public class UtilsKalimero2 {

	private static ArrayList<Double> disminucionAire;
	private static ArrayList<Double> disminucionSuelo;
	private double[] alcances;
	private double[][] pesosPase;
	private SituacionPartido sp;
	private Posicion destinoBalon;
	private Posicion remateCabeza;
	private Posicion puntoMedioRemate;
	private Posicion remateCabezaPortero;
	private double dx = 0.;
	private double dy = 0.;
	private double dz = 0.;
	private int porteroRival=99;
	private int iteraciones = 1;
	private int iteracionesDestinoBalon = 1;
	private int iteracionesCabeza = 1;
	private int iteracionesCabezaPortero = 1;
	
	private Posicion posicionBalon = new Posicion(Constantes.centroCampoJuego);
	private double alturaBalon = 0.;

	public UtilsKalimero2(TacticaDetalle td) {

		this.inicializaAlcances(td);
		this.inicializaVelocidades(td);
		this.inicializaPesosPase();
		// this.probar();

	}

	public UtilsKalimero2() {
	}

	public void setSituacionPartido(SituacionPartido sp) {
		this.sp = sp;
	}

	public void prepara() {

		this.dx = sp.balon().getX() - this.posicionBalon.getX();
		this.dy = sp.balon().getY() - this.posicionBalon.getY();
		this.dz = sp.alturaBalon() - this.alturaBalon;

		this.destinoBalon = calculaDestinoBalon(this.posicionBalon,
				this.alturaBalon, 0., dx, dy, dz);
		this.iteracionesDestinoBalon = this.iteraciones;
		
		this.remateCabeza = calculaDestinoBalon(this.posicionBalon,
				this.alturaBalon, Constantes.ALTURA_CONTROL_BALON, dx, dy, dz);
		this.iteracionesCabeza = this.iteraciones;
		
		this.remateCabezaPortero = calculaDestinoBalon(this.posicionBalon,
					this.alturaBalon, Constantes.ALTO_ARCO, dx, dy, dz);
		this.iteracionesCabezaPortero = this.iteraciones;
		
		this.puntoMedioRemate = new Posicion( (this.remateCabeza.getX() + this.destinoBalon.getX()) / 2., (this.remateCabezaPortero.getY() + this.destinoBalon.getY()) /2.);
		
		if( porteroRival == 99){
			for( int i = 0; i< 11;i++){
				if( sp.detalleJugadoresRivales()[i].esPortero()){
					porteroRival = i;
				}
			}
		}
		
		this.posicionBalon =sp.balon();
		this.alturaBalon = sp.alturaBalon();

	}



	private void inicializaAlcances(TacticaDetalle td) {
		alcances = new double[11];
		for (int i = 0; i <= 10; i++) {
			alcances[i] = this.alcance(1., td.getJugadores()[i].getRemate(),
					30.d) + 10.d;
		}
	}

	private void inicializaVelocidades(TacticaDetalle td) {

	}

	private void inicializaPesosPase() {
		pesosPase = new double[11][4];
		for (int i = 0; i < 11; i++) {
			pesosPase[i][0] = 1.; // al más lejano
			pesosPase[i][1] = 1.; // al más adelante
			pesosPase[i][2] = 3.; // al más libre
			pesosPase[i][3] = 7.; // puedo avanzar
		}
	}

	/**
	 * Calcula el destino de un balon elevado
	 * @param posicionBalon
	 * @param alturaBalon
	 * @param alturaDestino
	 * @param dx
	 * @param dy
	 * @param dz
	 * @return
	 */
	public Posicion calculaDestinoBalon(Posicion posicionBalon,
			double alturaBalon, double alturaDestino, double dx, double dy,
			double dz) {

		int iter = 1;

		double b = 2 * dz + Constantes.G;
		double c = 2 * (alturaBalon - alturaDestino);

		double r = Math.pow(b, 2) + 4 * Constantes.G * c;

		if (r >= 0) {
			iter = (int) Math.max(Math.round((b + Math.sqrt(r))
					/ (2 * Constantes.G)), Math.round((b - Math.sqrt(r))
					/ (2 * Constantes.G)));
		}
		if (iter < 1) {
			iter = 1;
		}
		double disminucion = 0.;
		if (dz == 0) {
			disminucion = getDisminucionSuelo(iter - 1);
		} else {
			disminucion = getDisminucionAire(iter - 1);
		}

		double x = posicionBalon.getX() + dx * disminucion;
		double y = posicionBalon.getY() + dy * disminucion;
		
		this.iteraciones = iter;
		
		return new Posicion(x, y);
	}

	/**
	 * Genera el sumatorio de las disminuciones hasta al menos la iteracion
	 * pedida y devuelve el valor de la iteracion pedida c⁰ + c¹ + c²+...
	 * 
	 * @param iteracion
	 * @return
	 */
	private static double getDisminucionAire(int iteracion) {
		if (disminucionAire == null) {
			disminucionAire = new ArrayList<Double>();
			disminucionAire.add(1.);
		}

		while (disminucionAire.size() <= iteracion) {
			disminucionAire.add(disminucionAire.get(disminucionAire.size() - 1)
					+ Math.pow(Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE,
							disminucionAire.size()));
		}

		return disminucionAire.get(iteracion);

	}

	/**
	 * Genera el sumatorio de las disminuciones hasta al menos la iteracion
	 * pedida y devuelve el valor de la iteracion pedida c⁰ + c¹ + c²+...
	 * 
	 * @param iteracion
	 * @return
	 */
	private static double getDisminucionSuelo(int iteracion) {
		if (disminucionSuelo == null) {
			disminucionSuelo = new ArrayList<Double>();
			disminucionSuelo.add(1.);
		}

		while (disminucionSuelo.size() <= iteracion) {
			disminucionSuelo.add(disminucionSuelo
					.get(disminucionSuelo.size() - 1)
					+ Math.pow(Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO,
							disminucionSuelo.size()));
		}

		return disminucionSuelo.get(iteracion);

	}

	/**
	 * Comprueba si la posicioón está dentro del campo
	 * @param posicion
	 * @return
	 */
	public static boolean isDentroCampo(Posicion posicion) {
		double mx = Constantes.ANCHO_CAMPO_JUEGO / 2;
		double my = Constantes.LARGO_CAMPO_JUEGO / 2;
		double x0 = posicion.getX(), y0 = posicion.getY();

		return !(x0 > mx || x0 < -mx || y0 > my || y0 < -my);
	}

	/**
	 * Calcula el alcance de un tiro elevado
	 * 
	 * @param fuerza
	 * @param fuerzaRemate
	 * @param anguloVertical
	 * @return
	 */
	private double alcance(double fuerza, double fuerzaRemate,
			double anguloVertical) {
		double vel = fuerza * Constantes.getVelocidadRemate(fuerzaRemate);

		double angVer = Math.min((Math.PI / 180.) * anguloVertical,
				Constantes.ANGULO_VERTICAL_MAX);
		
		double dx = vel * Math.cos(angVer) * 1;
		double dy = 0.;
		double dz = redondeaMultiplo(vel * Math.sin(angVer), Constantes.G);

		Posicion destino = this.calculaDestinoBalon(
				Constantes.centroCampoJuego, 0.d, 0.d, dx, dy, dz);

		return Constantes.centroCampoJuego.distancia(destino);
	}

	/**
	 * Determina el angulo con el que tiene que tirar el jugador
	 * @param jugador
	 * @return
	 */
	public double anguloTiroAGol(int jugador) {
		Posicion pos = sp.misJugadores()[jugador];
		Double error = Constantes.getErrorAngular(sp.detalleMisJugadores()[jugador].getPresicion()) * Math.PI;
		Double angulo;
		if(pos.getX() < Constantes.posteIzqArcoSup.getX()  ){
			angulo = pos.angulo(Constantes.posteIzqArcoSup) - error;
		}else if (pos.getX() > Constantes.posteDerArcoSup.getX()  ){
			angulo = pos.angulo(Constantes.posteDerArcoSup) + error;
		}else if( sp.rivales()[this.porteroRival].getX() > 0 ){
			angulo = pos.angulo(Constantes.posteIzqArcoSup) - error ;
		}else{
			angulo = pos.angulo(Constantes.posteDerArcoSup) + error ;
		}
		angulo =  angulo *  180d / Math.PI ;
//		System.out.println(" " + Constantes.posteIzqArcoSup + " - " +  pos.getX() + " - " + Constantes.posteDerArcoSup);
//		System.out.println("angulo " + angulo + " precision " + (error * 180.d /Math.PI) + " posicion " + pos);
//		System.out.println(" angulo poste izq" + (pos.angulo(Constantes.posteIzqArcoSup)* 180d / Math.PI)  ) ;
//		System.out.println(" angulo poste Der" + (pos.angulo(Constantes.posteDerArcoSup)* 180d / Math.PI)  ) ;
		return angulo;
	}
	
	/**
	 * fuerza con la que tirar a puerta
	 * @param jugador
	 * @return
	 */
	public double fuerzaTiroAGol(int jugador, double alturaInicial){
		return fuerzaTiro(jugador, Constantes.centroArcoSup.distancia( sp.misJugadores()[jugador]), 30., alturaInicial,Constantes.ALTO_ARCO);
	}

	public double fuerzaTiro(int jugador, double distancia, double angulo, double alturaInicio, double alturaFin){
		boolean log = false;
		boolean parar = false;
		double f = 1.d;
		double x = 0.;
		
		double remate = Constantes.getVelocidadRemate(sp.detalleMisJugadores()[jugador].getRemate());
		double angVer = Math.min( angulo * (Math.PI / 180.), Constantes.ANGULO_VERTICAL_MAX);
		
		double ctoVelx =remate * Math.cos(angVer);
		double ctoVelz = ctoVelx * Math.sin(angVer);
		
		double dx = f * ctoVelx;
		double dz = redondeaMultiplo(f * ctoVelz, Constantes.G);
		
		while(!parar && f > 0.01){
			int i=1;
			while( i<50 && distancia > x ){
				x = getDisminucionAire(i) * dx;
				i++;
			}
			i++;
			if( ( alturaInicio + i * dz - ( (i*i - i) * Constantes.G /2.) ) < alturaFin ){
				log(log," Altura Calculada:" + ( alturaInicio + i * dz - ( (i*i - i) * Constantes.G /2.))  + " fuerza " + f);
				parar = true;			
			}else{
				f= f - 0.01;
				dx = f * ctoVelx;
				dz = redondeaMultiplo(f * ctoVelz, Constantes.G);
				x = 0;
			}
			log(log," distancia : " + distancia + " distancia calculada " + this.calculaDestinoBalon(sp.misJugadores()[jugador], alturaInicio, alturaFin, dx, 0.d, dz).distancia(sp.misJugadores()[jugador]));
			log(log," destino Balon: " + this.calculaDestinoBalon(sp.misJugadores()[jugador], alturaInicio, alturaFin, dx, 0.d, dz));
		}
		
		return f;
	}
	
	private static double redondeaMultiplo(double valor, double divisor) {
		return Math.round(valor / divisor) * divisor;
	}

	/**
	 * decide a quien pasar
	 * 
	 * @param i
	 * @param jugadores
	 * @param rival
	 * @return
	 */
	public int pasarA(int i, Posicion[] jugadores, Posicion[] rival) {
		
		if ( jugadores[i].distancia(Constantes.centroArcoSup) < 20 && 
					( jugadores[i].angulo(Constantes.centroArcoSup) >= (Math.PI/6.)
						|| jugadores[i].angulo(Constantes.centroArcoSup) <= (Math.PI - Math.PI/6.) ) ) {
			return 11;
		}
		
		double valorMax = 0.;
		double valor = 0.;
		int pasarA = i;

		for (int j = 0; j <= 10; j++) {
			if (i != j && (jugadores[i].distancia(jugadores[j])) <= alcances[i]) {
				valor = pesosPase[i][0]
						* (jugadores[j].distancia(jugadores[i]) / alcances[i])
						+ pesosPase[i][1]
						* ((jugadores[j].getY() + jugadores[i].getY() + alcances[i]) / (2 * alcances[i]))
						+ this.rivalesCerca(i,Constantes.DISTANCIA_CONTROL_BALON * pesosPase[i][2])/11.d;
						
			}
			if (valor > valorMax) {
				pasarA = j;
				valorMax = valor;
			}

		}

		return pasarA;
	}
	
	private int rivalesCerca(int j, double distancia ){
		int sum =0;
		for(int i=0;i<11;i++){
			if( sp.rivales()[i].distancia(sp.misJugadores()[j]) < distancia){
				sum++;
			}
		}
		return 0;
	}
	
	public int iteracionesEnLlegar(int i, Posicion pos){
		int iteraciones = 0;
		
		iteraciones = (int)(pos.distancia(sp.misJugadores()[i]) / Constantes.getVelocidad(sp.detalleMisJugadores()[i].getVelocidad())) + 1;
		
		return iteraciones;
	}
	
	public boolean avanceConBalon(int j, Posicion pos){
		boolean log = false;
		double vel = Constantes.getVelocidad(sp.detalleMisJugadores()[j].getVelocidad());
		Posicion des  = sp.misJugadores()[j].moverAngulo(sp.misJugadores()[j].angulo(pos), vel);
		this.log(log," Posicion jugador:" + sp.misJugadores()[j]);
		this.log(log," Posicion destino:" + des);
		int[] cer = des.indicesMasCercanos(sp.rivales());
		boolean avance = true;
		for(int i=0;i<11;i++){
			this.log(log," distancia a Jugador " + cer[i] + " distancia " + sp.rivales()[cer[i]].distancia(des) );
			if (cer[i] != this.porteroRival && sp.rivales()[cer[i]].distancia(des) < ( vel + Constantes.DISTANCIA_CONTROL_BALON) * this.pesosPase[j][3] ){
				avance = false;
			}else if( cer[i] == this.porteroRival &&  sp.rivales()[cer[i]].distancia(des) < ( vel + Constantes.DISTANCIA_CONTROL_BALON_PORTERO) * this.pesosPase[j][3]){
				avance = false;
			}
		}
		return avance;
	}

	public Posicion getDestinoBalon() {
		return destinoBalon;
	}

	public Posicion getRemateCabeza() {
		return remateCabeza;
	}

	public Posicion getRemateCabezaPortero() {
		return remateCabezaPortero;
	}
	
	public Posicion getPuntoMedioRemate(){
		return this.puntoMedioRemate;
	}
	
	public int getIteracionesDestinoBalon() {
		return iteracionesDestinoBalon - 1;
	}

	public int getIteracionesCabeza() {
		return iteracionesCabeza - 1;
	}
	
	public int getIteracionesCabezaPortero() {
		return iteracionesCabezaPortero - 1;
	}
	
	
	public void log(boolean log,String cadena){
		if( log){
		//System.out.println(sp.iteracion() +" - " + cadena);
		}
	}
	
	public double balonCruzaPorteria(){
			
		double x = 0.;
	
		if ( dy != 0.){
			 x = sp.balon().getX() + sp.balon().getY() * dx /dy;
		}
		return x;
	}
	
	public boolean balonParado(){
		if( dx == 0 && dy==0){
			return true;
		}
		return false;
	}

	
}
